package com.sh.data.engine.domain.shims.sql;

import com.google.common.collect.Lists;
import com.sh.data.engine.domain.base.model.enums.Database;
import com.sh.data.engine.domain.shims.sql.select.RowMapper;
import org.apache.commons.lang3.StringUtils;

import java.sql.*;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

/**
 * @author: mengzheng.mhc
 * @date: 2024/6/5 15:22
 */
public class Context {

    private static final String SEPARATOR = " ";

    private static final String LEFT_BRACKET = "(";

    private static final String RIGHT_BRACKET = ")";

    public static final String CONNECTOR = ".";

    private String escape;

    private final Connection connection;

    private final List<StringBuilder> sqls;

    private StringBuilder currentSql;

    private final List<Object> parameters;

    private final Database database;

    private final transient Logger log;

    {
        this.log = Logger.getLogger(getClass().getName());
    }

    public Context(Context clone) {
        this.connection = clone.connection;
        this.parameters = new LinkedList<>(clone.parameters);
        this.sqls = Lists.newArrayList(new StringBuilder());
        this.currentSql = this.sqls.get(0);
        this.database = clone.database;
        this.escape = this.database.getEscape();
    }

    public Context(Database database, Connection connection) {
        this.connection = connection;
        this.database = database;
        this.escape = this.database.getEscape();
        this.sqls = Lists.newArrayList(new StringBuilder());
        this.currentSql = this.sqls.get(0);
        this.parameters = new LinkedList<>();
    }

    public void reset() {
        this.sqls.clear();
        this.parameters.clear();

        this.sqls.add(new StringBuilder());
        this.currentSql = this.sqls.get(0);
    }

    public List<StringBuilder> getSqls() {
        return sqls;
    }

    public StringBuilder getCurrentSql() {
        return currentSql;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        for (StringBuilder sql : this.sqls) {
            String s = sql.toString().trim();
            if (StringUtils.isBlank(s)) {
                continue;
            }
            if (!database.equals(Database.ORACLE) && !s.endsWith(";")) {
                s += ";";
            }
            stringBuilder.append(s).append("\n");
        }

        return stringBuilder.toString();
    }

    public <E> List<E> list(RowMapper<E> rowMapper) throws SQLException {
        List<E> result = new LinkedList<>();

        String sql = trimSemicolon(this.sqls.get(0).toString());

        try (PreparedStatement statement = prepareStatement(sql);
             ResultSet resultSet = statement.executeQuery()) {

            int rowNum = 0;

            while (resultSet.next()) {
                E obj = rowMapper.convert(resultSet, rowNum++);
                result.add(obj);
            }
        }

        return result;
    }

    public ResultSet list() throws SQLException {

        String sql = trimSemicolon(this.sqls.get(0).toString());

        PreparedStatement statement = prepareStatement(sql);
        ResultSet resultSet = statement.executeQuery();
        return resultSet;
    }

    public <E> E single(RowMapper<E> rowMapper) throws SQLException {
        E result = null;

        String sql = trimSemicolon(this.sqls.get(0).toString());

        try (PreparedStatement statement = prepareStatement(sql);
             ResultSet resultSet = statement.executeQuery()) {

            if (resultSet.next()) {
                result = rowMapper.convert(resultSet, 1);

                if (resultSet.next()) {
                    throw new SQLException("The query returned more than one result");
                }
            }
        }

        return result;
    }

    public void execute() throws SQLException {

        for (StringBuilder sql : this.sqls) {
            if (StringUtils.isBlank(sql)) {
                continue;
            }
            String s = trimSemicolon(sql.toString());
            try (final PreparedStatement preparedStatement = prepareStatement(s)) {
                preparedStatement.executeUpdate();
            }
        }
    }

    public Context append(Object expression) {
        this.currentSql.append(expression);
        return this;
    }

    public Context appendWithLeftBracket(Object expression) {
        this.currentSql.append(expression);
        this.currentSql.append(LEFT_BRACKET);
        return this;
    }

    public Context appendWithRightBracket(Object expression) {
        this.currentSql.append(expression);
        this.currentSql.append(RIGHT_BRACKET);
        return this;
    }

    public Context appendWithSeparator(Object expression) {
        this.currentSql.append(expression);
        this.currentSql.append(SEPARATOR);
        return this;
    }

    public Context appendWithEscape(Object expression) {
        this.currentSql.append(escape);
        this.currentSql.append(expression);
        this.currentSql.append(escape);
        return this;
    }

    public Context appendWithConnector(Object... expressions) {
        if (expressions != null) {
            for (Object expression : expressions) {
                this.currentSql.append(expression).append(CONNECTOR);
            }
            this.currentSql.deleteCharAt(this.currentSql.length() - CONNECTOR.length());
        }
        return this;
    }

    public Context deleteCharAt(int index) {
        this.currentSql.deleteCharAt(index);
        return this;
    }

    public Context newSeparator() {
        this.currentSql.append(SEPARATOR);
        return this;
    }

    public Context appendNewSql() {
        StringBuilder newSql = new StringBuilder();
        this.sqls.add(newSql);
        this.currentSql = newSql;
        return this;
    }

    public void addParameters(Object... parameters) {
        this.parameters.addAll(Arrays.asList(parameters));
    }

    public Database getDatabase() {
        return database;
    }

    public void colseConnection() throws SQLException {
        if (null != connection) {
            connection.close();
        }
    }

    public List<Object> getParameters() {
        return parameters;
    }

    public String getEscape() {
        return escape;
    }

    private PreparedStatement prepareStatement(String sql) throws SQLException {
        PreparedStatement statement = connection.prepareStatement(sql);

        int i = 1;
        for (Object parameter : parameters) {
            if (parameter instanceof Date) {
                if (parameter instanceof Timestamp) {
                    statement.setObject(i++, parameter);
                    continue;
                }
                parameter = new java.sql.Date(((Date) parameter).getTime());
            }
            statement.setObject(i++, parameter);
        }
        return statement;
    }

    /**
     * 去除sql后面的分号;
     *
     * @param s
     * @return
     */
    private String trimSemicolon(String s) {
        if (null == s) {
            return null;
        }
        s = s.trim();

        if (s.endsWith(";")) {
            s = s.substring(0, s.length() - 1);
        }

        return s;
    }
}
